package edu.gatech.mobile.scene.ui.image;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Range;
import org.opencv.core.Scalar;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import edu.gatech.mobile.scene.AppConstants;
import edu.gatech.mobile.scene.AppConstants.RELATIVE_TO_HORIZON;
import edu.gatech.mobile.scene.ApplicationContext;
import edu.gatech.mobile.scene.R;
import edu.gatech.mobile.scene.image.util.HistogramCompareUtil;
import edu.gatech.mobile.scene.image.util.MatUtil;
import edu.gatech.mobile.scene.model.ColorHistogram;
import edu.gatech.mobile.scene.model.ImageInfo;
import edu.gatech.mobile.scene.model.LabelInfo;
import edu.gatech.mobile.scene.model.MaskInfo;
import edu.gatech.mobile.scene.provider.impl.ImageInfoDBImpl;
import edu.gatech.mobile.scene.provider.impl.LabelInfoDBImpl;
import edu.gatech.mobile.scene.provider.impl.MaskInfoDBImpl;
import edu.gatech.mobile.scene.ui.image.helper.ColorPickerDialog;

public class ImageViewActivity extends Activity implements
		ColorPickerDialog.OnColorChangedListener, OnClickListener {

	private static final String TAG = ImageViewActivity.class.getName();
			
	private static final int COLOR_MENU_ID = Menu.FIRST;
	private static final int FINISH_LABEL_ID = Menu.FIRST + 1;

	private LabelInfoDBImpl labelDB;
	private Paint mPaint;
	private int width;
	private int height;
	private String currentLabel;
	private int maskCounter = -1;

	private DrawingOverlay mDraw;
	private Bitmap image;

	// private Mat labelImage;
	private List<Bitmap> labelImages;
	private List<String> maskNames;
	private List<String> labelNames;

	// Bitmap labelImage;
	private String imagePath;
	private List<int[]> labelMinMax;
	
	
	private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) {
		@Override
		public void onManagerConnected(int status) {
		   switch (status) {
		       case LoaderCallbackInterface.SUCCESS:
			      Log.i(TAG, "OpenCV loaded successfully");
			      // Create and set View
			      setUpViews();
			      break;
		       default:
			      super.onManagerConnected(status);
			      break;	
		   }
		    }
		};
	

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (!OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, this,
				mOpenCVCallBack)) {
			Log.e(TAG, "Cannot connect to OpenCV Manager");
		}
	}
	
	private void setUpViews(){
		setContentView(R.layout.image_view);

		imagePath = (String) getIntent().getStringExtra(
				AppConstants.BUNDLE_IMAGE_KEY);

		labelImages = new ArrayList<Bitmap>();
		maskNames = new ArrayList<String>();
		labelNames = new ArrayList<String>();
		labelMinMax = new ArrayList<int[]>();

		labelDB = new LabelInfoDBImpl(this);

		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setDither(true);
		mPaint.setColor(Color.TRANSPARENT);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.ROUND);
		mPaint.setStrokeCap(Paint.Cap.ROUND);
		mPaint.setStrokeWidth(AppConstants.PAINT_STROKE_WIDTH);
		// mPaint.setAlpha(0x80);
		width = 640;
		height = 480;

		Bitmap tempImage = BitmapFactory.decodeFile(imagePath);
		image = tempImage.copy(Bitmap.Config.ARGB_8888, true);
		tempImage.recycle();

		mDraw = new DrawingOverlay(this, mPaint, width, height, image,
				Bitmap.createBitmap(image.getWidth(), image.getHeight(),
						Bitmap.Config.ARGB_8888));
		addContentView(mDraw, new LayoutParams(width, height));

		populateLabelButtons();
	}
	

	private void populateLabelButtons() {
		List<LabelInfo> labels = labelDB.getLabels();
		for (LabelInfo label : labels) {
			Integer color = label.getColor();
			Button btn = new Button(this);
			btn.setText(label.getLabelName().toLowerCase());
			btn.setTag(label.getLabelName() + ":" + color);
			btn.setTextColor(color);
			btn.setOnClickListener(this);
			LinearLayout ll = (LinearLayout) findViewById(R.id.label);
			ll.addView(btn);
		}
	}

	@Override
	public void onClick(View v) {
		mDraw.setLabelSelected(true);

		String[] tokens;
		String values = (String) v.getTag();
		tokens = values.split(":");
		currentLabel = tokens[0];
		String colorString = tokens[1];
		int color = Integer.parseInt(colorString);
		mPaint.setColor(color);
		labelNames.add(currentLabel);
		String name = imagePath + "_" + currentLabel + ".png";
		if (!maskNames.contains(name)) {
			labelImages.add(Bitmap.createBitmap(image.getWidth(),
					image.getHeight(), Bitmap.Config.ARGB_8888));
			maskNames.add(name);
			int[] minMax = new int[4];
			minMax[0] = Integer.MAX_VALUE;
			minMax[1] = Integer.MAX_VALUE;
			minMax[2] = 0;
			minMax[3] = 0;
			labelMinMax.add(minMax);

			maskCounter++;
			mDraw.setNewBitmap(labelImages.get(maskCounter));
			mDraw.setMinMax(labelMinMax.get(maskCounter));
		}

	}

	@Override
	public void onResume() {
		super.onResume();
	}

	@Override
	public void onPause() {
		super.onPause();
	}

	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(0, COLOR_MENU_ID, 0, "Add New Label").setShortcut('3', 'c');
		menu.add(0, FINISH_LABEL_ID, 0, "Labeling Done").setShortcut('5', 'z');
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		mPaint.setXfermode(null);

		switch (item.getItemId()) {
		case COLOR_MENU_ID:
			final AlertDialog.Builder alert = new AlertDialog.Builder(this);
			final EditText input = new EditText(this);
			alert.setView(input);
			alert.setPositiveButton("Ok",
					new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialog,
								int whichButton) {
							String value = input.getText().toString().trim();
							LabelInfo label = labelDB.getLabelByName(value);
							if (label != null) {
								Toast.makeText(getApplicationContext(),
										"Label " + value + " already exist.",
										Toast.LENGTH_SHORT).show();
							} else {
								new ColorPickerDialog(ImageViewActivity.this,
										ImageViewActivity.this, mPaint
												.getColor(), value).show();
							}
						}
					});

			alert.setNegativeButton("Cancel",
					new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialog,
								int whichButton) {
							dialog.cancel();
						}
					});
			alert.show();
			return true;

		case FINISH_LABEL_ID:
			try {
				for (int i = 0; i < maskNames.size(); i++) {
					FileOutputStream out;
					out = new FileOutputStream(maskNames.get(i));
					labelImages.get(i).compress(Bitmap.CompressFormat.PNG, 90,
							out);
					System.out.println(maskNames.get(i) + " saved");
					labelImages.get(i).recycle();
				}
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
			
			FeatureExtractTask task = new FeatureExtractTask(this, imagePath, maskNames, labelNames, labelMinMax);
			task.execute();
			this.finish();
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	@Override
	public void onDestroy() {
		image.recycle();
		this.mDraw = null;
		super.onDestroy();
		System.gc();
	}

	public void colorChanged(int color, String labelName) {
		// mPaint.setColor(color);
		LabelInfo newLabel = new LabelInfo();
		newLabel.setColor(color);
		newLabel.setLabelName(labelName);
		labelDB.putItem(newLabel);
		Button btn = new Button(this);
		btn.setText(labelName.toLowerCase());
		btn.setTag(labelName + ":" + color);
		btn.setTextColor(color);
		btn.setOnClickListener(this);
		LinearLayout ll = (LinearLayout) findViewById(R.id.label);
		ll.addView(btn);
	}

	private class FeatureExtractTask extends AsyncTask<Void, Void, Void> {
		private Mat image;
		private ImageInfo imageInfo;
		private String imagePath;
		private List<Mat> labelImages;
		private List<String> maskNames;
		private List<String> labelNames;
		private List<int[]> labelMinMax;
		private ImageInfoDBImpl imageDB;
		private MaskInfoDBImpl maskDB;
		private Context appContext;

		public FeatureExtractTask(Context context, String imagePath, List<String> maskNames,
				List<String> labelNames, List<int[]> minMax) {
			
			appContext = context;
			imageDB = new ImageInfoDBImpl(context);
			maskDB = new MaskInfoDBImpl(context);
			this.image = Highgui.imread(imagePath);
			this.imagePath = imagePath;
			imagePath = null;
			this.imageInfo = imageDB.getImageByName(this.imagePath);
			this.labelImages = new ArrayList<Mat>();
			this.maskNames = new ArrayList<String>();
			this.labelMinMax = new ArrayList<int[]>();
			this.labelNames = new ArrayList<String>();
			for(String maskName : maskNames){
				this.maskNames.add(new String(maskName));
			}
			maskNames = null;
			
			for(String labelName : labelNames){
				this.labelNames.add(new String(labelName));
			}
			labelNames=null;
			
			for(int[] vals : minMax){
				this.labelMinMax.add(Arrays.copyOf(vals, vals.length));
			}
			minMax = null;
		}

		protected Void doInBackground(Void... params) {
			for(String maskName : maskNames){
				Mat mask = Highgui.imread(maskName);
				this.labelImages.add(mask);
			}					    
			Point[] horizonPoints = imageInfo.getHorizonPoints();
			Point point1 = horizonPoints[0];
			Point point2 = horizonPoints[1];
			
			double slope  = (point1.y-point2.y)*1.0/(point1.x-point2.x)*1.0;
			double height  = ((point2.y-point1.y)*point1.x)/(point2.x-point1.x);
			double intercept = point1.y-height;
						
			// Compute features for masks
			for(int i=0; i<maskNames.size(); i++){
				int [] minMax = labelMinMax.get(i);
				
				int xMin = minMax[0];
				int yMin = minMax[1];
				int xMax = minMax[2];
				int yMax = minMax[3];
				
				Mat mask = labelImages.get(i);				
				Mat grayMask = new Mat(mask.rows(), mask.cols(), CvType.CV_8UC1);
				Imgproc.cvtColor(mask, grayMask, Imgproc.COLOR_RGB2GRAY);
				mask.release();
				
				
				for(int xLoc=xMin; xLoc< xMax; xLoc+=AppConstants.BLOCK_SIZE){
					for(int yLoc=yMin; yLoc<yMax; yLoc+=AppConstants.BLOCK_SIZE){
						int xLimit = xLoc+AppConstants.BLOCK_SIZE;
						int yLimit = yLoc + AppConstants.BLOCK_SIZE;
						boolean empty = true;
						
						if (yLimit <=image.height() && xLimit <= image.width()) {
						
							for(int x =xLoc; x<xLimit; x++){
								for(int y =yLoc; y<yLimit; y++){
									if(x<image.cols() && y < image.rows() && x>0 && y>0){
										byte[] data = new byte[3];
										grayMask.get(y, x, data);
										if(notBlack(data)){
											empty=false;
										}		
									}
								}
							}
						
							if(!empty){
								
								MaskInfo maskInfo = new MaskInfo();
								maskInfo.setLabelInfo(labelDB.getLabelByName(labelNames.get(i)));
								maskInfo.setOrigImageName(imagePath);
								maskInfo.setMaskName(maskNames.get(i));
								maskInfo.setPixelLocX(xLoc);
								maskInfo.setPixelLocY(yLoc);
								
								RELATIVE_TO_HORIZON horizonPosition;
								
								int leftY = (int) (xLoc*slope+intercept);
								int rightY = (int) ((xLoc+AppConstants.BLOCK_SIZE)*slope+ intercept);
								
								if(yLoc > leftY && yLoc >rightY && (yLoc+AppConstants.BLOCK_SIZE) > leftY && (yLoc+AppConstants.BLOCK_SIZE)> rightY){
									horizonPosition = RELATIVE_TO_HORIZON.BELOW;
								}
								else if(yLoc < leftY && yLoc < rightY && (yLoc+AppConstants.BLOCK_SIZE) < leftY && (yLoc+AppConstants.BLOCK_SIZE) <rightY ){
									horizonPosition = RELATIVE_TO_HORIZON.ABOVE;
								}
								else{
									horizonPosition = RELATIVE_TO_HORIZON.ON;
								}
								maskInfo.setHorizonPosition(horizonPosition);
								Mat colorMat = image.submat(new Range(yLoc, yLoc+AppConstants.BLOCK_SIZE), new Range(xLoc, xLoc + AppConstants.BLOCK_SIZE));
								Mat maskMat = grayMask.submat(new Range(yLoc, yLoc+AppConstants.BLOCK_SIZE), new Range(xLoc, xLoc + AppConstants.BLOCK_SIZE));
								Imgproc.threshold(maskMat, maskMat, 0, 255, Imgproc.THRESH_BINARY);
								List<Mat> histograms =HistogramCompareUtil.RGBHistCalc(colorMat, maskMat);
								ColorHistogram colorHistogram = new ColorHistogram(histograms);							
								maskInfo.setHistograms(colorHistogram);
								maskDB.putMask(maskInfo);							
								for(Mat histo : histograms){
									histo.release();
								}
							}
						}
					}						
				}
			}
			// update image to be used for classification
			imageInfo.setUse(true);
			imageDB.updateImage(imageInfo);
			
			
			
			ApplicationContext context =  (ApplicationContext) appContext.getApplicationContext();
	        ImageInfo newImageInfo = imageDB.getImageByName(imagePath);
	        
	        int aboveCount = maskDB.getMaskCountWithHorizonAndImage(RELATIVE_TO_HORIZON.ABOVE, newImageInfo.getImagePath());
	        int belowCount = maskDB.getMaskCountWithHorizonAndImage(RELATIVE_TO_HORIZON.BELOW, newImageInfo.getImagePath());;
	        int onCount = maskDB.getMaskCountWithHorizonAndImage(RELATIVE_TO_HORIZON.ON, newImageInfo.getImagePath());;
	        
	        Mat aboveData;
	    	Mat onData;
	    	Mat belowData;
	    	
			aboveData= new Mat (aboveCount, 60, CvType.CV_32F);
			onData = new Mat (onCount, 60, CvType.CV_32F);
			belowData = new Mat (belowCount, 60, CvType.CV_32F);
			Scalar scalar = new Scalar(0);
			
			aboveData.setTo(scalar);
			onData.setTo(scalar);
			belowData.setTo(scalar);
	        
			int aboveMaskIndex=0;
			int onMaskIndex=0;
			int belowMaskIndex=0;
			
	        
	        for (MaskInfo mask : newImageInfo.getMasks()) {
				RELATIVE_TO_HORIZON position = mask.getHorizonPosition();
				mask.getHistograms().normalizeColorHistogram();	
				if(position.equals(RELATIVE_TO_HORIZON.BELOW)){
					context.getBelowMaskImages().add(mask.getId());
					context.getBelowMaskColors().add(mask.getLabelInfo().getColor());
					MatUtil.fillMatcherMat(belowData, mask, belowMaskIndex); 
					belowMaskIndex++;
				}
				
				if(position.equals(RELATIVE_TO_HORIZON.ON)){
					context.getOnMaskImages().add(mask.getId());
					context.getOnMaskColors().add(mask.getLabelInfo().getColor());
					MatUtil.fillMatcherMat(onData, mask, onMaskIndex);
					onMaskIndex++;
				
				}
				if(position.equals(RELATIVE_TO_HORIZON.ABOVE)){
					context.getAboveMaskImages().add(mask.getId());
					context.getAboveMaskColors().add(mask.getLabelInfo().getColor());
					MatUtil.fillMatcherMat(aboveData, mask, aboveMaskIndex);
					aboveMaskIndex++;
				}
			}
	        
	        
	        
			List<Mat> belowList = new ArrayList<Mat>();
			for(Mat mat : context.getBelowMatcher().getTrainDescriptors()){
				belowList.add(mat);
			}
			belowList.add(belowData);
			context.getBelowMatcher().add(belowList);	
			System.out.println("Below data added");
	        
			
			
			List<Mat> onList = new ArrayList<Mat>();
			for(Mat mat : context.getOnMatcher().getTrainDescriptors()){
				onList.add(mat);
			}
			
			onList.add(onData);
			context.getOnMatcher().add(onList);
			System.out.println("On data added");
			
			
	        
	        List<Mat> aboveList = new ArrayList<Mat>();
	        for(Mat mat : context.getAboveMatcher().getTrainDescriptors()){
	        	aboveList.add(mat);
	        }
			aboveList.add(aboveData);
			context.getAboveMatcher().add(aboveList);
			System.out.println("Above data added");

			return null;
		}

		private boolean notBlack(byte[] data) {
			for(int i=0; i<data.length; i++){
				if((int)data[i] != 0){
					return true;
				}
			}
			return false;
		}

	}

}
